﻿using System;


namespace advadev.CanOpen
{

    /// <summary>
    /// Represents a synchronization producer service
    /// </summary>
    public class SynchronizationProducerService : ServiceBase
    {

        /// <summary>for internal use</summary>
        private int _nextCounter = 0;
        /// <summary>for internal use</summary>
        private DateTime _nextSynchronizationTimeUtc = DateTime.MinValue;
        /// <summary>for internal use</summary>
        private IServiceTimer _serviceTimer;


        /// <summary>
        /// Initializes a new instance of the SynchronizationProducerService
        /// class by the given parameter
        /// </summary>
        /// <param name="objectDictionaryContainer">
        /// Object dictionary container (must not be null)
        /// </param>
        /// <param name="serviceTimer">
        /// Service timer
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Will be thrown if the object dictionary container or the service
        /// timer is null
        /// </exception>
        public SynchronizationProducerService(ICanOpenObjectDictionaryContainer objectDictionaryContainer, IServiceTimer serviceTimer)
            : base(objectDictionaryContainer)
        {
            // check parameter
            if (serviceTimer == null)
            {
                throw new ArgumentNullException("serviceTimer");
            }

            // initialization
            _serviceTimer = serviceTimer;
            // TODO: get NMT service and reset counter for -> boot-up, stopped -> pre-operational, stopped -> operational to 1
        }


        /// <summary>
        /// Gets a flag indicating whether the syncrhonization service is
        /// enabled
        /// </summary>
        private bool IsEnabled
        {
            get
            {
                if (CobIdSynchronizationMessageParameter != null && CommunicationCyclePeriodParameter != null && CommunicationCyclePeriodParameter.Value != 0)
                {
                    return (((CobIdSynchronizationMessageParameter.Value & 0x40000000) >> 30) == 1);
                }
                return false;
            }
        }

        /// <summary>
        /// Gets or sets the COB-ID synchronization message parameter
        /// </summary>
        private CanOpenUnsigned32 CobIdSynchronizationMessageParameter
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the communication cycle period parameter
        /// </summary>
        private CanOpenUnsigned32 CommunicationCyclePeriodParameter
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the synchronous counter overflow value parameter
        /// </summary>
        private CanOpenUnsigned8 SynchronousCounterOverflowValueParameter
        {
            get;
            set;
        }


        /// <summary>
        /// Handles the "ValueChanged" event of the COB-ID synchronization
        /// message parameter
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void CobIdSynchronizationMessageParameter_ValueChanged(object sender, ValueChangedEventArgs args)
        {
            bool oldFlag = ((((ulong)args.OldValue & 0x40000000) >> 30) == 1);
            bool currentFlag = ((((ulong)args.CurrentValue & 0x40000000) >> 30) == 1);
            if (oldFlag == false && currentFlag == true)
            {
                _nextCounter = 1;
            }
        }

        /// <summary>
        /// Handles the "ValueChanged" event of the communication cycle period
        /// parameter
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void CommunicationCyclePeriodParameter_ValueChanged(object sender, ValueChangedEventArgs args)
        {
            if ((ulong)args.OldValue == 0 && (ulong)args.CurrentValue != 0)
            {
                _nextCounter = 1;
            }
        }

        /// <summary>
        /// Handles the "TimeFrameStarted" event of the service timer
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void serviceTimer_TimeFrameStarted(object sender, ServiceTimerTimeFrameStartedEventArgs args)
        {
            // check state
            if (State == ServiceState.Stopped)
            {
                return;
            }

            // transmit synchronization
            if (IsEnabled)
            {
                long communicationCyclePeriodInMicroSeconds = (long)CommunicationCyclePeriodParameter.Value;
                long communicationCyclePeriodInTicks = communicationCyclePeriodInMicroSeconds * (TimeSpan.TicksPerMillisecond / 1000);
                if (_nextSynchronizationTimeUtc == DateTime.MinValue)
                {
                    _nextSynchronizationTimeUtc = args.FrameStartTimeUtc + new TimeSpan(communicationCyclePeriodInTicks);
                }
                while (_nextSynchronizationTimeUtc < args.FrameStartTimeUtc)
                {
                    _nextSynchronizationTimeUtc += new TimeSpan(communicationCyclePeriodInTicks);
                }
                if (args.FrameStartTimeUtc <= _nextSynchronizationTimeUtc && _nextSynchronizationTimeUtc < args.FrameStartTimeUtc + args.FrameSize)
                {
                    if (State == ServiceState.Started)
                    {
                        UInt16 cobId = (UInt16)(CobIdSynchronizationMessageParameter.Value & 0x1FFFFFFF);
                        byte? counter = null;
                        if (SynchronousCounterOverflowValueParameter != null && SynchronousCounterOverflowValueParameter.Value > 0)
                        {
                            counter = (byte)_nextCounter;
                        }
                        SynchronizationObjectWriteTransfer transfer = new SynchronizationObjectWriteTransfer(counter, cobId);
                        //Logging.Logger.Log("SERVICE", Logging.LogEntityType.InternalFlow, "REQUEST FOR FRAME: " + timeToLogString(args.FrameStartTimeUtc));
                        RequestTransfer(transfer);
                        _nextCounter++;
                        if (SynchronousCounterOverflowValueParameter != null && _nextCounter > (int)SynchronousCounterOverflowValueParameter.Value)
                        {
                            _nextCounter = 1;
                        }
                    }
                    _nextSynchronizationTimeUtc += new TimeSpan(communicationCyclePeriodInTicks);
                }
            }
            else
            {
                _nextSynchronizationTimeUtc = DateTime.MinValue;
            }
        }

        // -->> HACK
        static private string timeToLogString(DateTime time)
        {
            long floorTicks = (long)Math.Floor((double)time.Ticks / (double)TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond;
            long microSeconds = ((time.Ticks - floorTicks) / (TimeSpan.TicksPerMillisecond / 1000));
            if (microSeconds == 1000)
            {
                microSeconds = 0;
            }
            // -->> HACK
            //microSeconds = 0;
            // <<-- HACK
            return time.ToLongTimeString() + "." + string.Format("{0:000}", time.Millisecond) + "." + string.Format("{0:000}", microSeconds);
        }
        // <<-- HACK


        #region ServiceBase implementation
        /// <summary>
        /// Starts the (stopped) service
        /// </summary>
        override public void Start()
        {
            if (State == ServiceState.Stopped)
            {
                _nextCounter = 1;
                _nextSynchronizationTimeUtc = DateTime.MinValue;
                CobIdSynchronizationMessageParameter = ObjectDictionaryContainer.GetCanOpenObject(0x1005, 0x00) as CanOpenUnsigned32;
                if (CobIdSynchronizationMessageParameter != null)
                {
                    CobIdSynchronizationMessageParameter.ValueChanged += new ValueChangedEventHandler(CobIdSynchronizationMessageParameter_ValueChanged);
                }
                CommunicationCyclePeriodParameter = ObjectDictionaryContainer.GetCanOpenObject(0x1006, 0x00) as CanOpenUnsigned32;
                if (CommunicationCyclePeriodParameter != null)
                {
                    CommunicationCyclePeriodParameter.ValueChanged += new ValueChangedEventHandler(CommunicationCyclePeriodParameter_ValueChanged);
                }
                SynchronousCounterOverflowValueParameter = ObjectDictionaryContainer.GetCanOpenObject(0x1019, 0x00) as CanOpenUnsigned8;
                _serviceTimer.TimeFrameStarted += new ServiceTimerTimeFrameStartedEventHandler(serviceTimer_TimeFrameStarted);
                State = ServiceState.Started;
            }
        }

        /// <summary>
        /// Pauses the (started) service
        /// </summary>
        override public void Pause()
        {
            if (State == ServiceState.Started)
            {
                State = ServiceState.Paused;
            }
        }

        /// <summary>
        /// Resumes the (paused) service
        /// </summary>
        override public void Resume()
        {
            if (State == ServiceState.Paused)
            {
                State = ServiceState.Started;
            }
        }

        /// <summary>
        /// Stops the (started or paused) service
        /// </summary>
        override public void Stop()
        {
            if (State == ServiceState.Started || State == ServiceState.Paused)
            {
                State = ServiceState.Stopped;
                _serviceTimer.TimeFrameStarted -= new ServiceTimerTimeFrameStartedEventHandler(serviceTimer_TimeFrameStarted);
                SynchronousCounterOverflowValueParameter = null;
                if (CommunicationCyclePeriodParameter != null)
                {
                    CommunicationCyclePeriodParameter.ValueChanged -= new ValueChangedEventHandler(CommunicationCyclePeriodParameter_ValueChanged);
                }
                CommunicationCyclePeriodParameter = null;
                if (CobIdSynchronizationMessageParameter != null)
                {
                    CobIdSynchronizationMessageParameter.ValueChanged -= new ValueChangedEventHandler(CobIdSynchronizationMessageParameter_ValueChanged);
                }
                CobIdSynchronizationMessageParameter = null;
                _nextSynchronizationTimeUtc = DateTime.MinValue;
                _nextCounter = 0;
            }
        }
        #endregion ServiceBase implementation

    }

}
